package repository

import (
	"context"
	"errors"

	"gitee.com/flycash/simple-im/internal/domain"

	"github.com/ecodeclub/ekit/slice"

	"gitee.com/flycash/simple-im/internal/repository/dao"
	"github.com/gotomicro/ego/core/elog"
)

var (
	ErrDuplicateChannel      = dao.ErrDuplicateRecord
	ErrChannelNotFound       = dao.ErrChannelRecordNotFound
	ErrChannelMemberNotFound = dao.ErrChannelMemberRecordNotFound
)

type ChannelRepository interface {
	CreateChannel(ctx context.Context, c domain.Channel) (domain.Channel, error)
	CreateOneToOneChannel(ctx context.Context, c domain.Channel, member, targetMember domain.Member) (domain.Channel, error)
	CreateChannelMember(ctx context.Context, cid int64, m domain.Member) error
	DeleteChannelByCID(ctx context.Context, cid int64) error
	DeleteChannelMemberByCID(ctx context.Context, cid int64) error
	DeleteChannelMemberByCIDAndUID(ctx context.Context, cid, uid int64) error
	UpdateChannelInfoByCID(ctx context.Context, c domain.Channel) (domain.Channel, error)
	UpdateChannelMemberByCIDAndUID(ctx context.Context, cid int64, m domain.Member) (domain.Member, error)
	FindChannelByCID(ctx context.Context, id int64) (domain.Channel, error)
	FindChannelByCIDAndUID(ctx context.Context, cid, uid int64) (domain.Channel, error)
	FindAllChannelsByUID(ctx context.Context, uid int64) ([]domain.Channel, error)
	FindOneToOneChannelByUID(ctx context.Context, uid, targetUID int64) (domain.Channel, error)
	FindChannelMembersByCID(ctx context.Context, cid int64) ([]domain.Member, error)
	FindChannelMemberByCIDAndUID(ctx context.Context, cid, uid int64) (domain.Member, error)
	// GetInviteLink(ctx context.Context, key string) (cid, uid int64, err error)
	// SetInviteLink(ctx context.Context, key string, val string) error
	// DeleteInviteLink(ctx context.Context, key string) error
	FindByCIDs(ctx context.Context, cids []int64) ([]domain.Channel, error)
}

type channelRepository struct {
	channelDAO       dao.ChannelDAO
	channelMemberDAO dao.ChannelMemberDAO
	logger           *elog.Component
}

func (r *channelRepository) FindByCIDs(ctx context.Context, cids []int64) ([]domain.Channel, error) {
	cs, err := r.channelDAO.FindByIDs(ctx, cids)
	if err != nil {
		return nil, err
	}
	return slice.Map[dao.Channel, domain.Channel](cs, func(_ int, src dao.Channel) domain.Channel {
		return r.channelEntityToDomain(src)
	}), nil
}

func (r *channelRepository) CreateChannel(ctx context.Context, c domain.Channel) (domain.Channel, error) {
	resEntity, err := r.channelDAO.Insert(ctx, r.channelDomainToEntity(c))
	if err != nil {
		return domain.Channel{}, err
	}
	res := r.channelEntityToDomain(resEntity)
	return res, nil
}

func (r *channelRepository) CreateOneToOneChannel(ctx context.Context, c domain.Channel, member, targetMember domain.Member) (domain.Channel, error) {
	chEntity, memberEntity, err := r.channelDAO.InsertOneToOne(ctx, r.channelDomainToEntity(c),
		r.memberDomainToEntity(member, 0), r.memberDomainToEntity(targetMember, 0))
	if err != nil {
		return domain.Channel{}, err
	}
	ch := r.channelEntityToDomain(chEntity)
	ch.Self = r.memberEntityToDomain(memberEntity)
	return ch, nil
}

func (r *channelRepository) CreateChannelMember(ctx context.Context, cid int64, m domain.Member) error {
	err := r.channelMemberDAO.Insert(ctx, r.memberDomainToEntity(m, cid))
	if errors.Is(err, ErrDuplicateChannel) {
		return ErrDuplicateChannel
	}
	return err
}

func (r *channelRepository) DeleteChannelByCID(ctx context.Context, cid int64) error {
	return r.channelDAO.DeleteByID(ctx, cid)
}

func (r *channelRepository) DeleteChannelMemberByCID(ctx context.Context, cid int64) error {
	return r.channelMemberDAO.DeleteByCID(ctx, cid)
}

func (r *channelRepository) DeleteChannelMemberByCIDAndUID(ctx context.Context, cid, uid int64) error {
	return r.channelMemberDAO.DeleteByCIDAndUID(ctx, cid, uid)
}

func (r *channelRepository) UpdateChannelInfoByCID(ctx context.Context, c domain.Channel) (domain.Channel, error) {
	res, err := r.channelDAO.UpdateInfoByID(ctx, r.channelDomainToEntity(c))
	if err != nil {
		return domain.Channel{}, err
	}
	return r.channelEntityToDomain(res), nil
}

func (r *channelRepository) UpdateChannelMemberByCIDAndUID(ctx context.Context, cid int64, m domain.Member) (domain.Member, error) {
	res, err := r.channelMemberDAO.UpdateByCIDAndUID(ctx, r.memberDomainToEntity(m, cid))
	if err != nil {
		return domain.Member{}, err
	}
	return r.memberEntityToDomain(res), nil
}

func (r *channelRepository) FindChannelByCID(ctx context.Context, id int64) (domain.Channel, error) {
	res, err := r.channelDAO.FindByID(ctx, id)
	if err != nil {
		return domain.Channel{}, err
	}
	resDomain := r.channelEntityToDomain(res)
	return resDomain, nil
}

func (r *channelRepository) FindOneToOneChannelByUID(ctx context.Context, uid, targetUID int64) (domain.Channel, error) {
	res, err := r.channelDAO.FindOneToOne(ctx, uid, targetUID)
	if err != nil {
		return domain.Channel{}, err
	}
	ch := r.channelEntityToDomain(res)
	member, err := r.FindChannelMemberByCIDAndUID(ctx, ch.ID, uid)
	if err != nil {
		return ch, err
	}
	ch.Self = member
	return ch, nil
}

func (r *channelRepository) FindChannelByCIDAndUID(ctx context.Context, cid, uid int64) (domain.Channel, error) {
	ch, err := r.FindChannelByCID(ctx, cid)
	if err != nil {
		return domain.Channel{}, err
	}
	member, err := r.FindChannelMemberByCIDAndUID(ctx, cid, uid)
	if err != nil {
		return domain.Channel{}, err
	}
	ch.Self = member
	return ch, nil
}

func (r *channelRepository) FindAllChannelsByUID(ctx context.Context, uid int64) ([]domain.Channel, error) {
	cms, err := r.channelMemberDAO.FindAllChannelMembersByUID(ctx, uid)
	if err != nil {
		return []domain.Channel{}, err
	}
	if len(cms) == 0 {
		return []domain.Channel{}, nil
	}
	cids := make([]int64, 0, len(cms))
	cmsMap := make(map[int64]dao.ChannelMember)
	for _, cm := range cms {
		cids = append(cids, cm.ChannelID)
		cmsMap[cm.ChannelID] = cm
	}
	channels, err := r.channelDAO.FindByIDs(ctx, cids)
	if err != nil {
		return []domain.Channel{}, err
	}
	res := make([]domain.Channel, 0, len(cms))
	for _, c := range channels {
		dc := r.channelEntityToDomain(c)
		cm := cmsMap[c.ID]
		dc.Self = r.memberEntityToDomain(cm)
		res = append(res, dc)
	}
	return res, nil
}

func (r *channelRepository) FindChannelMembersByCID(ctx context.Context, cid int64) ([]domain.Member, error) {
	cms, err2 := r.channelMemberDAO.FindMembersByCID(ctx, cid)
	if err2 != nil {
		return nil, err2
	}
	const startCap = 100
	res := make([]domain.Member, 0, startCap)
	for _, cm := range cms {
		res = append(res, r.memberEntityToDomain(cm))
	}
	return res, nil
}

func (r *channelRepository) FindChannelMemberByCIDAndUID(ctx context.Context, cid, uid int64) (domain.Member, error) {
	res, err := r.channelMemberDAO.FindByCIDAndUID(ctx, cid, uid)
	return r.memberEntityToDomain(res), err
}

//func (r *channelRepository) GetInviteLink(ctx context.Context, key string) (cid, uid int64, err error) {
//	cid, uid, err = r.cache.GetInviteLink(ctx, key)
//	if errors.Is(err, cache.ErrKeyNotExist) {
//		return 0, 0, ErrChannelNotFound
//	}
//	return cid, uid, err
//}

//func (r *channelRepository) SetInviteLink(ctx context.Context, key, val string) error {
//	return r.cache.SetInviteLink(ctx, key, val)
//}
//
//func (r *channelRepository) DeleteInviteLink(ctx context.Context, key string) error {
//	return r.cache.DeleteInviteLink(ctx, key)
//}

func (r *channelRepository) channelDomainToEntity(c domain.Channel) dao.Channel {
	return dao.Channel{
		ID:   c.ID,
		Name: c.Name,
		Type: c.Type,
	}
}

func (r *channelRepository) channelEntityToDomain(c dao.Channel) domain.Channel {
	return domain.Channel{
		ID:   c.ID,
		Name: c.Name,
		Type: c.Type,
	}
}

func (r *channelRepository) memberDomainToEntity(m domain.Member, cid int64) dao.ChannelMember {
	return dao.ChannelMember{
		ChannelID:         cid,
		UID:               m.ID,
		Note:              m.Note,
		NicknameInChannel: m.NicknameInChannel,
		Priority:          m.Priority,
		Mute:              m.Mute,
		JoinTime:          m.JoinTime,
		Ctime:             0,
		Utime:             0,
	}
}

func (r *channelRepository) memberEntityToDomain(c dao.ChannelMember) domain.Member {
	return domain.Member{
		ID:                c.UID,
		NicknameInChannel: c.NicknameInChannel,
		Note:              c.Note,
		Avatar:            "",
		Priority:          c.Priority,
		Mute:              c.Mute,
		JoinTime:          c.JoinTime,
	}
}

func NewChannelRepository(channelDAO dao.ChannelDAO, channelMemberDAO dao.ChannelMemberDAO) ChannelRepository {
	res := &channelRepository{
		channelDAO:       channelDAO,
		channelMemberDAO: channelMemberDAO,
		logger:           elog.EgoLogger.With(elog.FieldComponent("ChannelRepository")),
	}
	return res
}
